#include <Unit.h>
#include <iostream>

using namespace std;

#define FEET2MINUNIT 1
#define YARD2MINUNIT (3*FEET2MINUNIT)
#define MILE2MINUNIT (1760*YARD2MINUNIT)

#define OZ2MINUNIT   1
#define TBSP2MINUNIT (2*OZ2MINUNIT)
#define TSP2MINUNIT  (3*TBSP2MINUNIT)

UnitAmount Unit::convert2MinUnit(const UnitAmount& value) const
{
    return factor_m * value;
}

UnitAmount Unit::convertFromMinUnit(const UnitAmount& value) const
{
    return value / factor_m;
}

UnitAmount Unit::remainInMinUnit(const UnitAmount& amountInMinUnit) const
{
    return amountInMinUnit % factor_m;
}

template <class T>
void Unit::formatUnit(std::ostream& out, const UnitAmount& amount,T& unitSets) const
{
   UnitAmount value = convert2MinUnit(amount);
   class T::iterator elemIterator;
   for(elemIterator=unitSets.begin();elemIterator!=unitSets.end();++elemIterator)
   {
      UnitAmount myVal = elemIterator->convertFromMinUnit(value);
      if (myVal > 0)
        out << myVal << " " << *elemIterator;
      value = elemIterator->remainInMinUnit(value);
      if (value > 0)
        out << " ";
   }
}

template <class T>
void Unit::formatZeroUnit(std::ostream& out, T& unitSets) const
{
   out << "0" << " " << unitSets.back();
}

Unit::Unit(const UnitAmount& factor, const string& name) :
    factor_m(factor), name_m(name)
{
}

std::ostream&
operator<<(std::ostream& out, const Unit& unit)
{
    out << unit.name_m;
    return out;
}

#define DEFINE_LENGTH_UNIT(unitname) \
const LengthUnit& \
LengthUnit::define##unitname() \
{\
    static const LengthUnit unit(unitname##2MINUNIT,#unitname);\
    return unit;\
}

DEFINE_LENGTH_UNIT(MILE)
DEFINE_LENGTH_UNIT(YARD)
DEFINE_LENGTH_UNIT(FEET)

const LengthUnit& LengthUnit::minUnit()
{
  return defineFEET();
}

lengthUnitSets& LengthUnit::unitSet()
{
  static lengthUnitSets allUnitSets;

  if(allUnitSets.empty())
  {
     allUnitSets.push_back(MILE);
     allUnitSets.push_back(YARD);
     allUnitSets.push_back(FEET);
  }
  return allUnitSets;
}

void LengthUnit::formatUnitSets(std::ostream& out, const UnitAmount& amount) const
{
  if (amount == 0)
    formatZeroUnit<lengthUnitSets>(out,LENGTH_UNITSET);
  else
    formatUnit<lengthUnitSets>(out,amount,LENGTH_UNITSET);
}

const Unit& LengthUnit::getMinUnit(void) const
{
   return LENGTH_MINUNIT;
}

LengthUnit::LengthUnit(const UnitAmount& factor, const string& name) : Unit(factor,name)
{
}

#define DEFINE_VOLUMN_UNIT(unitname) \
const VolumnUnit& \
VolumnUnit::define##unitname() \
{\
    static const VolumnUnit unit(unitname##2MINUNIT,#unitname);\
    return unit;\
}

DEFINE_VOLUMN_UNIT(TSP)
DEFINE_VOLUMN_UNIT(TBSP)
DEFINE_VOLUMN_UNIT(OZ)

const VolumnUnit& VolumnUnit::minUnit()
{
  return defineOZ();
}

volumnUnitSets& VolumnUnit::unitSet()
{
  static volumnUnitSets allUnitSets;

  if(allUnitSets.empty())
  {
     allUnitSets.push_back(TSP);
     allUnitSets.push_back(TBSP);
     allUnitSets.push_back(OZ);
  }
  return allUnitSets;
}

void VolumnUnit::formatUnitSets(std::ostream& out, const UnitAmount& amount) const
{
  if (amount == 0)
    formatZeroUnit<volumnUnitSets>(out,VOLUMN_UNITSET);
  else
    formatUnit<volumnUnitSets>(out,amount,VOLUMN_UNITSET);
}

const Unit& VolumnUnit::getMinUnit(void) const
{
   return VOLUMN_MINUNIT;
}

VolumnUnit::VolumnUnit(const UnitAmount& factor, const string& name) : Unit(factor,name)
{
}

